﻿/*
Copyright (C) 2009  Tomasz Chrzanowski

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

namespace Com.TomCdc.ChangeDataCapture
{
    /// <summary>
    /// Defines the method to populate <see cref="DataGridView"/> object with the change history record.
    /// </summary>
    class ViewBuilder
    {
        /// <summary>
        /// Represents <see cref="DataGridView"/> object which will represent changed data captured to the user.
        /// </summary>
        private readonly DataGridView _presentationGrid;

        /// <summary>
        /// The provider communicating with store database, in this class used to pull schema info of tracked table.
        /// </summary>
        private readonly SourceRecordHistoryProvider _historyProvider;

        /// <summary>
        /// The colour used to represent cells containing the change transaction information.
        /// </summary>
        private readonly Color _cellColorTranInfo;

        /// <summary>
        /// The colour used to represent a table fields which value has changed.
        /// </summary>
        private readonly Color _cellColorChangedValue;

        /// <summary>
        /// The colour used to represent a table field which was tracked at the time of the change transaction capture.
        /// </summary>
        private readonly Color _cellColorActiveTracking;

        /// <summary>
        /// Defines mapping of the changed data column id to <see cref="DataGridView"/> object column.
        /// </summary>
        private Dictionary<int, int> _mapColIdToGridColumn;

        /// <summary>
        /// Defines mapping of the source column ordinar position (the store DB specific) to <see cref="DataGridView"/> object column.
        /// </summary>
        private Dictionary<int, int> _mapColOrdinarToGridColumn;

        /// <summary>
        /// The table name which was displayed last time in the <see cref="DataGridView"/> object.
        /// </summary>
        private string _lastDisplayedTable;

        /// <summary>
        /// Initializes a new instance of <see cref="ViewBuilder"/> class with provider of source records and 
        /// the <see cref="DataGridView"/> object representing grid where the change history will be presented.
        /// </summary>
        /// <param name="histProvider">A <see cref="SourceRecordHistoryProvider"/> object used to query information 
        /// about source table.</param>
        /// <param name="presentationGrid">A <see cref="DataGridView"/> object used for result presentation.</param>
        public ViewBuilder(SourceRecordHistoryProvider histProvider, DataGridView presentationGrid)
        {
            // validates input arguments
            if (histProvider == null)
            {
                throw new ArgumentNullException("histProvider");
            }

            if (presentationGrid == null)
            {
                throw new ArgumentNullException("presentationGrid");
            }

            _historyProvider = histProvider;
            _presentationGrid = presentationGrid;

            // prepares background colors for cells with transaction info, changed values and active transaction at 
            // the time of a capture
            _cellColorTranInfo = Color.FromArgb(200, 200, 255);
            _cellColorChangedValue = Color.FromArgb(255, 255, 180);
            _cellColorActiveTracking = Color.FromArgb(255, 210, 210);

            _lastDisplayedTable = "";
        }

        /// <summary>
        /// Creates grid columns representing information about a change transaction.
        /// </summary>
        private void BuildTransactionInfoColHeaders()
        {
            // removes all columns from the view
            _presentationGrid.Columns.Clear();

            // adds transaction info columns
            _presentationGrid.Columns.Add("TranId", "Change Tran ID");
            _presentationGrid.Columns.Add("UserName", "User Name");
            _presentationGrid.Columns.Add("HostName", "Host Name");
            _presentationGrid.Columns.Add("AppName", "Application Name");
            _presentationGrid.Columns.Add("ChangeDate", "Change Date");
            _presentationGrid.Columns.Add("Spid", "MsSql Pid");
            _presentationGrid.Columns.Add("CmdType", "Command Type");
       
            _presentationGrid.Columns["ChangeDate"].Frozen = true;
        }

        /// <summary>
        /// Creates grid columns representing source table columns.
        /// </summary>
        /// <param name="srcRecord">A <see cref="SourceRecord"/> object, contains source table name for which columns
        /// will be created.</param>
        private void BuildTableColHeadres(SourceRecord srcRecord)
        {
            if (!_lastDisplayedTable.Equals(srcRecord.SourceTableName))
            {

                _presentationGrid.Columns.Clear();

                BuildTransactionInfoColHeaders();
                
                DataTable srcTableColumns = _historyProvider.GetTableColumns(srcRecord.SourceTableName);

                _mapColIdToGridColumn = new Dictionary<int, int>(srcTableColumns.Rows.Count);

                _mapColOrdinarToGridColumn = new Dictionary<int, int>(srcTableColumns.Rows.Count);

                // adds columns of source table to grid view and builds mapping src col ID to grid ID
                foreach (DataRow row in srcTableColumns.Rows)
                {
                    int gridColIdx = _presentationGrid.Columns.Add((string)row["Name"], (string)row["Name"]);
                    _mapColIdToGridColumn[(int)row["ID"]] = gridColIdx;
                    _mapColOrdinarToGridColumn[(int)row["OrdinalPosition"]] = gridColIdx;
                }

                _lastDisplayedTable = srcRecord.SourceTableName;
            }
        }

        /// <summary>
        /// Populates <see cref="DataGridView"/> with the change data history
        /// provided in <paramref name="srcRecordHistory"/>.
        /// </summary>
        /// <param name="srcRecord">A <see cref="SourceRecord"/> object containing table name.</param>
        /// <param name="srcRecordHistory">A <see cref="DataTable"/> object containing the change history.</param>
        public void PopulateGridView(SourceRecord srcRecord, DataTable srcRecordHistory)
        {
            // validates input arguments
            if (srcRecord == null)
            {
                throw new ArgumentNullException("srcRecord");
            }

            /*
            if (srcRecord.SourceRowId < 0)
            {
                throw new ArgumentOutOfRangeException("The SourceRowId property of " +
                    "srcRecord argument cannot be less than 0.");
            }
            */ 

            // removes all rows and columns from grid view
            _presentationGrid.Rows.Clear();

            // creats columns in the data grid if not created or new source table is being processed
            BuildTableColHeadres(srcRecord);

            // defines mapping of the changeTransactionId to grid view row
            Dictionary<Int64, int> mapTranIdToGridRow = new Dictionary<long, int>();

            // populates change transaction info
            using (DataTableReader srcRecHistReader = srcRecordHistory.CreateDataReader())
            {
                while (srcRecHistReader.Read())
                {
                    // prepares data reader column id
                    Int64 tranId = Convert.ToInt64(srcRecHistReader["ChangeTransactionId"]);

                    // adds transaction row to the grid if not existing
                    if (!mapTranIdToGridRow.ContainsKey(tranId))
                    {
                        int rowIdx = _presentationGrid.Rows.Add();
                        mapTranIdToGridRow[tranId] = rowIdx;
                        _presentationGrid.Rows[rowIdx].Cells["TranId"].Value = srcRecHistReader["ChangeTransactionId"];
                        _presentationGrid.Rows[rowIdx].Cells["UserName"].Value = srcRecHistReader["UserName"];
                        _presentationGrid.Rows[rowIdx].Cells["HostName"].Value = srcRecHistReader["HostName"];
                        _presentationGrid.Rows[rowIdx].Cells["AppName"].Value = srcRecHistReader["AppName"];
                        _presentationGrid.Rows[rowIdx].Cells["ChangeDate"].Value = srcRecHistReader["ChangeDate"];
                        _presentationGrid.Rows[rowIdx].Cells["Spid"].Value = srcRecHistReader["Spid"];
                        _presentationGrid.Rows[rowIdx].Cells["CmdType"].Value = srcRecHistReader["CmdType"];

                        // sets background color for transaction information columns
                        for (int colIdx = 0; colIdx <= 6; colIdx++)
                        {
                            _presentationGrid.Rows[rowIdx].Cells[colIdx].Style.BackColor = _cellColorTranInfo;
                        }

                        BitArray trackingMask = CommonHelper.ConvertToBitArray((string)srcRecHistReader["TrackingMask"]);
                        for (int i = 0; i < trackingMask.Length; i++)
                        {
                            // checks if column was tracked at the time of row capture and if the map from column ordinar 
                            // to grid column exists (that's safety check in case of no consistent tacking mask value with 
                            // columns definition table
                            if (trackingMask[i] && _mapColOrdinarToGridColumn.ContainsKey(i+1))
                            {
                                _presentationGrid.Rows[rowIdx].Cells[_mapColOrdinarToGridColumn[i + 1]].Style.BackColor =
                                    _cellColorActiveTracking;
                            }

                        }
                    }
                }
            }

            // populates change data values
            using (DataTableReader srcRecHistReader = srcRecordHistory.CreateDataReader())
            {
                // prepares data reader columns ids
                int colSourceColumnIdIdx = srcRecHistReader.GetOrdinal("SourceColumnId");
                int colChangeTransactionIdIdx = srcRecHistReader.GetOrdinal("ChangeTransactionId");
                int colSourceFieldValueIdx = srcRecHistReader.GetOrdinal("SourceFieldValue");

                // prepares font for null data values
                Font nullFieldFont = new Font(_presentationGrid.Font, FontStyle.Italic);

                int tranRowIdx, dataColIdx;
                
                // iterates through all changed data history
                while (srcRecHistReader.Read())
                {
                    // prepares grid view row and column index for change data insertion
                    tranRowIdx = mapTranIdToGridRow[srcRecHistReader.GetInt64(colChangeTransactionIdIdx)];
                    dataColIdx = _mapColIdToGridColumn[srcRecHistReader.GetInt32(colSourceColumnIdIdx)];

                    // marks cell with italic "NULL" string, when null value, otherwise ordinar font
                    if (srcRecHistReader.IsDBNull(colSourceFieldValueIdx))
                    {
                        _presentationGrid.Rows[tranRowIdx].Cells[dataColIdx].Value = "NULL";
                        _presentationGrid.Rows[tranRowIdx].Cells[dataColIdx].Style.Font = nullFieldFont;
                    }
                    else
                    {
                        _presentationGrid.Rows[tranRowIdx].Cells[dataColIdx].Value =
                            srcRecHistReader.GetValue(colSourceFieldValueIdx);
                    }

                    // changes background colour of a cell to indicate that the change took place
                    _presentationGrid.Rows[tranRowIdx].Cells[dataColIdx].Style.BackColor = _cellColorChangedValue;
                }
            }
        }
    }
}
